package org.nobody.mallb2c.mallproduct.web;

import org.nobody.mallb2c.mallproduct.entity.CategoryEntity;
import org.nobody.mallb2c.mallproduct.service.CategoryService;
import org.nobody.mallb2c.mallproduct.vo.Catelog2Vo;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author 3489309323@qq.com
 * @create 2020-09-08-8:53
 */
@Controller
public class IndexController {
  @Autowired
  CategoryService categoryService;

  @Autowired
  RedissonClient redissonClient;

  @Autowired
  StringRedisTemplate redisTemplate;

  @GetMapping({"/", "/index.html"})
  public String indexPage(Model model) {
    //TODO 1、查出所有1级分类
    List<CategoryEntity> categoryEntities = categoryService.getLevel1Categorys();

    model.addAttribute("categorys", categoryEntities);

    return "index";
    // ThymeleafProperties.java↓
    // 默认前缀: classpath:/templates/
    // 默认后缀: .html
  }

  @ResponseBody
  @GetMapping("/index/catalog.json")
  public Map<String, List<Catelog2Vo>> getCatalogJson() {
    Map<String, List<Catelog2Vo>> map = categoryService.getCatalogJson();
    return map;
  }

  @ResponseBody
  @GetMapping("/hello")
  public String hello(){
    //①可重入锁(Reentrant Lock)
    //方法A里调用方法B,A用l1加锁,B也可以用l1加锁

    //因为Rlock继承了juc的锁(java.util.concurrent.locks)
    //故juc怎么用RLock就怎么用

    //1、 获取一把锁, 只要锁的名字一样, 就是同一把锁
    RLock lock = redissonClient.getLock("my-lock");

    //2、 加锁

    //阻塞式等待,会在这里一直等直到拿到锁(和自旋方式相区别(本项目中搜索自旋关键字))
    //默认为30s 会在业务期间自动续期(由Redisson内部提供的一个监控的看门狗提供支持)
    //但只要运行完成,就不会给当前锁续期 即使不手动解锁 锁也会在30s后自动删除
    //可以通过修改Config.lockWatchdog.Timeout来另行指定超时时间
    //lock.lock();

    //10s自动解锁
    //这种方式不会自动续期,一般使用这种方式,因为如果30s还不能完成,说明业务早完了,很有问题
    lock.lock(30, TimeUnit.SECONDS);

    /* 总结
      如果传了锁的超时时间,就发送给redis执行脚本进行占锁,默认超时就是我们指定的时间

      如果我们未指定超时时间,就使用30*1000【LockWatchdogTimeout看门狗的默认时间】
        占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】
        这几个定时任务的执行频率是你设置的超时时间(internalLockLeaseTime)的1/3(默认即为10s)
    * */

    try {
      System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
      try {
        Thread.sleep(30000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }finally {
      System.out.println("释放锁..." + Thread.currentThread().getId());
      lock.unlock();

      //todo
      //lock.tryLock
      /*try {
        boolean b = lock.tryLock(100, 100, TimeUnit.SECONDS);//第一个参数是最多等待的时间,和lock.lock会一直等待直到拿到锁不同
      } catch (InterruptedException e) {
        e.printStackTrace();
      }*/

      //----------------------------------------

      //②公平锁(Fair Lock)
      //RLock lock2 = redissonClient.getFairLock("my-lock");
      //lock2.lock();
      //接下来 和 可重入锁 用法一样


    }

    return "hello";
  }



  /*
    ③读(共享锁)写(独享、互斥、排它锁)锁
    只要有写 都必须等待 无论先读还是先写

    但读并不影响读(相当于无锁)

问
1.如果处于写锁时，别的用户肯定不能写入，我想问，此刻可以读取数据吗？
2.如果处于读锁时，别的用户也可以读取，但是我想问，别的用户可以写入吗？
3.如果不能写入的话，那么如果每时每刻都有用户在读取，则永远轮不到有写入的机会呀？
4.如果可以写入的话，那么读锁有什么意义呢？也就是说，相当于读锁什么也不限制呀？

答
1. 被写锁（排它）锁定时，其它会话（用户）不能读取数据
2. 处于读锁时，别的会话也可以申请读锁，也可以读取，但是读锁在释放前，别的用户得不到相同资源的写锁。
3. 不能写入，每时每刻都有用户读取，但并不一定读取相同粒度的数据，比如锁定的是若干数据行，不是整张表。一旦锁释放，就有写入的机会。你说的情况会发生在粒度较大的锁。有的数据库这方面处理的不太好，比如sqlite
4. 不作答(颗粒度问题)。
  * */
  @GetMapping("/write")
  @ResponseBody
  public String writeValue(){
    String s = UUID.randomUUID().toString();
    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
    RLock rLock = readWriteLock.writeLock();
    //1、 写数据就加写锁, 读数据就加读锁
    rLock.lock();

    try {
      Thread.sleep(30000);
      redisTemplate.opsForValue().set("writeValue",s);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }finally {
      rLock.unlock();
    }

    return s;
  }

  @GetMapping("/read")
  @ResponseBody
  public String readValue(){
    String s = "";

    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
    RLock rLock = readWriteLock.readLock();
    rLock.lock();

    try {
      s = redisTemplate.opsForValue().get("writeValue");
    } finally {
      rLock.unlock();
    }

    return s;
  }


  /**
   * ④分布式信号量(可以做分布式限流,限制每一个应用的流量)
   *
   * 假设测试redis数据库里有一个key叫park
   * 其值为3
   *
   * park -> 3
   */
  @GetMapping("/park")
  @ResponseBody
  public String park() throws InterruptedException {
    RSemaphore park = redissonClient.getSemaphore("park");

    park.acquire();//获取一个信号,占一个车位,即park对应的key的value-1
    return "ok";

    //和acquire方法不一样,acquire会一直等,直到有空车位.而tryAcquire会直接返回,有空位就占位返回true,没有就直接返回false
    //boolean b = park.tryAcquire();
    //if(b){
    // //执行业务
    //}else {
      //提示
    //}
    //return "ok=>"+b;
  }

  @GetMapping("/go")
  @ResponseBody
  public String go(){
    RSemaphore park = redissonClient.getSemaphore("park");
    park.release();//释放一个车位,即 park 对应的key的value+1

    return "ok";
  }


  /**
   * ⑤闭锁
   *
   * 5个班全部走完,我们可以锁大门
   */
  @GetMapping("/lockDoor")
  @ResponseBody
  public String lockDoor() throws InterruptedException {
    RCountDownLatch door = redissonClient.getCountDownLatch("door");
    door.trySetCount(5);
    door.await(); //等待闭锁都完成
    return "放假了...";
  }

  @GetMapping("/gogogo/{id}")
  @ResponseBody
  public String gogogo(@PathVariable("id") Long id) {
    RCountDownLatch door = redissonClient.getCountDownLatch("door");
    door.countDown(); //计数减一
    return id + "班的人都走了...";
  }
}
